/**
 *  Copyright (c) 2014 The xmlepf project.  All rights reserved.
 *
 *  This file is part of xmlepf (Event Driven Testing Framework).
 *
 *  xmlepf is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  xmlepf is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with xmlepf.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  This software consists of voluntary contributions made by many
 *  individuals.  For exact contribution history, see the revision
 *  history and logs, available at http://code.google.com/p/xmlepf.
 */
package org.xmlepf.xml.adapter.saxon;

import java.io.ByteArrayInputStream;
import java.util.LinkedList;
import java.util.List;
import javax.xml.transform.stream.StreamSource;
import net.sf.saxon.Configuration;
import net.sf.saxon.event.Builder;
import net.sf.saxon.event.PipelineConfiguration;
import net.sf.saxon.om.NodeInfo;
import net.sf.saxon.query.QueryResult;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.tree.tiny.TinyBuilder;
import net.sf.saxon.type.Type;
import org.xmlepf.utils.Sys;
import org.xmlepf.xml.adapter.NodeSerializer;
import org.xmlepf.xml.adapter.XMLNode;

/**
 * Todo: the NodeSerializer should check the instance type before serializing
 */
public class NodeSerializerImp implements NodeSerializer {

    @Override
    public String serialize(List<XMLNode> nodes) throws XPathException {
        StringBuilder nodesString = new StringBuilder(1024 * 10); // todo: make this a config param

        for (XMLNode nodeInfo : nodes) {
            nodesString.append(serializeNode(nodeInfo));
        }
        return nodesString.toString();
    }

    private String serializeNode(XMLNode xmlNode) throws XPathException {
        NodeInfo nodeInfo = (NodeInfo) xmlNode.getNativeNode();
        if (nodeInfo.getNodeKind() == Type.ELEMENT
                || nodeInfo.getNodeKind() == Type.DOCUMENT) {
            return QueryResult.serialize(nodeInfo) + Sys.getProperty("line.separator");
        }
        if ((nodeInfo.getNodeKind() == Type.ATTRIBUTE)
                || (nodeInfo.getNodeKind() == Type.TEXT)) {
            return nodeInfo.getStringValue();
        }
        throw new XPathException("Unexpected node type \"" + nodeInfo.getNodeKind() + "\" encountered during serialization");
    }

    @Override
    public List<String> serializeAsList(List<XMLNode> nodes) throws XPathException {
        List<String> nodesStringList = new LinkedList<String>();

        for (XMLNode nodeInfo : nodes) {
            nodesStringList.add(serializeNode(nodeInfo));
        }
        return nodesStringList;
    }

    @Override
    public String serialize(XMLNode node) throws XPathException {
        return serializeNode(node);
    }

    @Override
    public XMLNode deserialize(String xml, Configuration configuration) throws Exception {
        StreamSource source = new StreamSource(new ByteArrayInputStream(xml.getBytes("UTF-8")));
        return new XMLNodeImp(configuration.buildDocument(source));
    }

    @Override
    public XMLNode mergeNodes(List<XMLNode> nodes, Configuration configuration) throws Exception {
        Builder docBuilder = new TinyBuilder();

        PipelineConfiguration pipelineConfiguration = configuration.makePipelineConfiguration();

        docBuilder.setPipelineConfiguration(pipelineConfiguration);
        docBuilder.open();
        docBuilder.startDocument(0);

        for (XMLNode xmlNode : nodes) {
            NodeInfo nodeInfo = (NodeInfo) xmlNode.getNativeNode();
            nodeInfo.copy(docBuilder, 0, 0);
        }

        docBuilder.endDocument();
        docBuilder.close();

        return new XMLNodeImp(docBuilder.getCurrentRoot());
    }
}
